#if !BESTHTTP_DISABLE_SIGNALR_CORE using System; using System.Collections.Generic; using BestHTTP.PlatformSupport.Memory; namespace BestHTTP.SignalRCore { public enum TransportTypes { #if !BESTHTTP_DISABLE_WEBSOCKET WebSocket, #endif LongPolling } public enum TransferModes { Binary, Text } public enum TransportStates { Initial, Connecting, Connected, Closing, Failed, Closed } /// <summary> /// Possible states of a HubConnection /// </summary> public enum ConnectionStates { Initial, Authenticating, Negotiating, Redirected, Reconnecting, Connected, CloseInitiated, Closed } /// <summary> /// States that a transport can goes trough as seen from 'outside'. /// </summary> public enum TransportEvents { /// <summary> /// Transport is selected to try to connect to the server /// </summary> SelectedToConnect, /// <summary> /// Transport failed to connect to the server. This event can occur after SelectedToConnect, when already connected and an error occurs it will be a ClosedWithError one. /// </summary> FailedToConnect, /// <summary> /// The transport successfully connected to the server. /// </summary> Connected, /// <summary> /// Transport gracefully terminated. /// </summary> Closed, /// <summary> /// Unexpected error occured and the transport can't recover from it. /// </summary> ClosedWithError } public interface ITransport { TransferModes TransferMode { get; } TransportTypes TransportType { get; } TransportStates State { get; } string ErrorReason { get; } event Action<TransportStates, TransportStates> OnStateChanged; void StartConnect(); void StartClose(); void Send(BufferSegment bufferSegment); } public interface IEncoder { BufferSegment Encode<T>(T value); T DecodeAs<T>(BufferSegment buffer); object ConvertTo(Type toType, object obj); } public sealed class StreamItemContainer<T> { public readonly long id; public List<T> Items { get; private set; } public T LastAdded { get; private set; } public bool IsCanceled; public StreamItemContainer(long _id) { this.id = _id; this.Items = new List<T>(); } public void AddItem(T item) { if (this.Items == null) this.Items = new List<T>(); this.Items.Add(item); this.LastAdded = item; } } struct CallbackDescriptor { public readonly Type[] ParamTypes; public readonly Action<object[]> Callback; public CallbackDescriptor(Type[] paramTypes, Action<object[]> callback) { this.ParamTypes = paramTypes; this.Callback = callback; } } internal struct InvocationDefinition { public Action<Messages.Message> callback; public Type returnType; } internal sealed class Subscription { public List<CallbackDescriptor> callbacks = new List<CallbackDescriptor>(1); public void Add(Type[] paramTypes, Action<object[]> callback) { this.callbacks.Add(new CallbackDescriptor(paramTypes, callback)); } public void Remove(Action<object[]> callback) { int idx = -1; for (int i = 0; i < this.callbacks.Count && idx == -1; ++i) if (this.callbacks[i].Callback == callback) idx = i; if (idx != -1) this.callbacks.RemoveAt(idx); } } public sealed class HubOptions { /// <summary> /// When this is set to true, the plugin will skip the negotiation request if the PreferedTransport is WebSocket. Its default value is false. /// </summary> public bool SkipNegotiation { get; set; } /// <summary> /// The preferred transport to choose when more than one available. Its default value is TransportTypes.WebSocket. /// </summary> public TransportTypes PreferedTransport { get; set; } /// <summary> /// A ping message is only sent if the interval has elapsed without a message being sent. Its default value is 15 seconds. /// </summary> public TimeSpan PingInterval { get; set; } /// <summary> /// The maximum count of redirect negoitiation result that the plugin will follow. Its default value is 100. /// </summary> public int MaxRedirects { get; set; } public HubOptions() { this.SkipNegotiation = false; #if !BESTHTTP_DISABLE_WEBSOCKET this.PreferedTransport = TransportTypes.WebSocket; #else this.PreferedTransport = TransportTypes.LongPolling; #endif this.PingInterval = TimeSpan.FromSeconds(15); this.MaxRedirects = 100; } } public interface IRetryPolicy { /// <summary> /// This function must return with a delay time to wait until a new connection attempt, or null to do not do another one. /// </summary> TimeSpan? GetNextRetryDelay(RetryContext context); } public struct RetryContext { /// <summary> /// Previous reconnect attempts. A successful connection sets it back to zero. /// </summary> public uint PreviousRetryCount; /// <summary> /// Elapsed time since the original connection error. /// </summary> public TimeSpan ElapsedTime; /// <summary> /// String representation of the connection error. /// </summary> public string RetryReason; } public sealed class DefaultRetryPolicy : IRetryPolicy { private static TimeSpan?[] DefaultBackoffTimes = new TimeSpan?[] { TimeSpan.Zero, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30), null }; TimeSpan?[] backoffTimes; public DefaultRetryPolicy() { this.backoffTimes = DefaultBackoffTimes; } public DefaultRetryPolicy(TimeSpan?[] customBackoffTimes) { this.backoffTimes = customBackoffTimes; } public TimeSpan? GetNextRetryDelay(RetryContext context) { if (context.PreviousRetryCount >= this.backoffTimes.Length) return null; return this.backoffTimes[context.PreviousRetryCount]; } } } #endif